package com.danicsoft.daide.command;

import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;

import com.danicsoft.daide.serverclient.VariantInfo;
import com.danicsoft.daide.token.IntegerToken;
import com.danicsoft.daide.token.StandardToken;
import com.danicsoft.daide.token.Token;
import com.danicsoft.daide.token.TokenMap;
import com.danicsoft.daide.token.TokenMap.Parameters;
import com.danicsoft.daide.token.TokenGroup;



/**
 * Implements the HLO command. From Eric Wald's <a
 * href="http://pydip.brainshell.org/docs/syntax.html">protocol specification</a>,
 * v0.14:
 * <h4><a name="HLO">server_message = HLO</a> (power) (passcode) (variant)</h4>
 * <p>
 * The server sends this to each client when the game starts. <b>power</b> is
 * the power to be played by this client, or UNO for an observer. <b>passcode</b>
 * is an integer value which is required to rejoin the game. <b>variant</b> is
 * of the form <b>(variant_option) (variant_option) ...</b>, and contains the
 * details of any options for the game. For example:
 * </p>
 * <p>
 * <b>HLO (ENG) (1234) ((LVL 20) (MTL 1200))</b> <br>
 * You are England. Your passcode is 1234. This is a level 20 game, with 20
 * minutes (1200 seconds) per movement turn.
 * </p>
 * <p>
 * <b>variant_option</b> can be any of the following:
 * </p>
 * <ul>
 * <!--
 * <h4>n = number</h4>
 * -->
 * <li><b>variant_option = LVL n</b> &mdash; game is of syntax level n. This
 * variant option will always be specified.</li>
 * <!--
 * <h4>seconds = number</h4>
 * -->
 * <li><b>variant_option = MTL seconds</b> &mdash; maximum number of seconds
 * available for movement turns. If not specified, there is no time limit.</li>
 * <li><b>variant_option = RTL seconds</b> &mdash; maximum number of seconds
 * available for retreat turns. If not specified, there is no time limit.</li>
 * <li><b>variant_option = BTL seconds</b> &mdash; maximum number of seconds
 * available for build turns. If not specified, there is no time limit.</li>
 * <li><b>variant_option = DSD</b> &mdash; deadline stops on disconnection. If
 * a player disconnects when it has orders due, the deadline timer stops.</li>
 * <li><b><a name="AOA">variant_option = AOA</a></b> &mdash; any orders
 * accepted. Any orders which match the syntax for an order will be accepted,
 * whether they are legal or not. If not specified, then only orders which could
 * be valid will be accepted.</li>
 * </ul>
 * <!--
 * <h4>variant_option != Parameters any_token_sequence</h4>
 * -->
 * <p>
 * The variant options will always be specified in the order they appear in this
 * document. <!-- Other parameters may appear as a <b>variant option</b>; if
 * so, this message syntax may be changed according to the rules for each
 * parameter. -->
 * </p>
 * <!--
 * <h4>client_request = HLO</h4>
 * -->
 * <p>
 * The client can send the command <b>HLO</b> to the server with no arguments
 * at any time. The server will reply by sending another copy of the <b>HLO</b>
 * message, or <b>REJ (HLO)</b> if the game hasn't started.
 * </p>
 * 
 * @author Daniel Yule (daniel.yule@gmail.com)
 * @version 1.0
 */
public class HelloCommand extends QueryableCommand implements VariantInfo {

	/**
	 * The power we are going to be playing.
	 */
	public StandardToken power;

	/**
	 * The passcode we can use to re-connect to the server later if we lose our
	 * connection.
	 */
	public int passcode;

	/**
	 * A list of options that specify information about this game. Read from and
	 * written to after and before doIO, discarded afterwards.
	 */
	private List<TokenGroup> variantOptions;

	/**
	 * The syntax level this game will be played at. If this is above the level
	 * the client can handle, any commands higher than the ones it is capable of
	 * understanding should be silently ignored.
	 */
	public int syntaxLevel = 0;

	/**
	 * The number of seconds after a turn starts that the client has to submit
	 * movement orders.
	 */
	public int movementTimeLimit = -1;

	/**
	 * The number of seconds after a turn starts that the client has to submit
	 * retreat orders.
	 */
	public int retreatTimeLimit = -1;

	/**
	 * The number of seconds after a turn starts that the client has to submit
	 * build orders.
	 */
	public int buildTimeLimit = -1;

	/**
	 * If true, the deadline timer stops when a client disconnects.
	 */
	public boolean deadlineDisconnect = false;

	/**
	 * If true then any order, legal or not, will be accepted.
	 */
	public boolean anyOrdersAccepted = false;

	/**
	 * Construct a new HelloCommand from the tokens specified. After loading
	 * from the <code>TokenGroup</code>, it reads from the
	 * <code>variantOptions</code> list and sets its member options
	 * accordingly. This is a rare command that needs specific input and output
	 * code, instead of generalized IO code.
	 * 
	 * @param tokens
	 *        The <code>TokenGroup</code> this command will load from
	 * @throws ParseException
	 *         If there was a problem parsing the tokens.
	 */

	public HelloCommand(TokenGroup tokens) throws ParseException {
		super(tokens);
		syntaxLevel = getVariantInteger(TokenMap.getParameterToken(Parameters.LVL)) / 10 * 10;
		if (syntaxLevel < 0) {
			throw new ParseException("Required a syntax level option.");
		}
		movementTimeLimit = getVariantInteger(TokenMap.getParameterToken(Parameters.MTL));

		retreatTimeLimit = getVariantInteger(TokenMap.getParameterToken(Parameters.RTL));

		buildTimeLimit = getVariantInteger(TokenMap.getParameterToken(Parameters.BTL));

		deadlineDisconnect = isVariantPresent(TokenMap.getParameterToken(Parameters.DSD));

		anyOrdersAccepted = isVariantPresent(TokenMap.getParameterToken(Parameters.AOA));

	}

	/**
	 * Construct a <code>HelloCommand</code> from the given data.
	 * 
	 * @param myPower
	 *        The power we are to be playing.
	 * @param myPasscode
	 *        The passcode to use if we reconnect later
	 * @param info
	 *        The information for this variant
	 */
	public HelloCommand(StandardToken myPower, int myPasscode, VariantInfo info) {
		super(false);
		power = myPower;
		passcode = myPasscode;
		anyOrdersAccepted = info.isAnyOrdersAccepted();
		buildTimeLimit = info.getBuildTimeLimit();
		deadlineDisconnect = info.isDeadlineDisconnect();
		movementTimeLimit = info.getMovementTimeLimit();
		retreatTimeLimit = info.getRetreatTimeLimit();
		syntaxLevel = info.getSyntaxLevel();
	}

	/**
	 * Constructs a new HelloCommand that can be used as a query to the server.
	 */
	public HelloCommand() {

	}

	/**
	 * Create the list of variant options for output and then output this
	 * command in the standard way.
	 * 
	 * @return A <code>TokenGroup</code> that contains this command.
	 * @see com.danicsoft.daide.command.Command#output()
	 */
	@Override
	public TokenGroup output() {
		syntaxLevel = syntaxLevel / 10 * 10;
		syntaxLevel = syntaxLevel < 0 ? 0 : syntaxLevel;
		variantOptions = new ArrayList<TokenGroup>();
		variantOptions.add(createOption(Parameters.LVL, syntaxLevel));

		if (movementTimeLimit > 0) {
			variantOptions.add(createOption(Parameters.MTL, movementTimeLimit));
		}

		if (retreatTimeLimit > 0) {
			variantOptions.add(createOption(Parameters.RTL, retreatTimeLimit));
		}

		if (buildTimeLimit > 0) {
			variantOptions.add(createOption(Parameters.BTL, buildTimeLimit));
		}

		if (deadlineDisconnect) {
			variantOptions.add(createOption(Parameters.DSD, -1));
		}

		if (anyOrdersAccepted) {
			variantOptions.add(createOption(Parameters.AOA, -1));
		}

		return super.output();
	}

	/**
	 * Create a variant options from the parameters specified. A variant option
	 * is a <code>TokenGroup</code> with the first <code>Token</code> being
	 * a parameter token and the second <code>Token</code> being either
	 * non-existent or an <code>IntegerToken</code>
	 * 
	 * @param optParam
	 *        The parameter for this option.
	 * @param val
	 *        The value for this option. If it is less than 0, no
	 *        <code>IntegerToken</code> is added.
	 * @return A <code>TokenGroup</code> representing the specified option.
	 */
	private TokenGroup createOption(byte optParam, int val) {
		TokenGroup rGroup = new TokenGroup();
		rGroup.add(TokenMap.getParameterToken(optParam));
		if (val >= 0)
			rGroup.add(new IntegerToken(val));

		return rGroup;
	}

	@Override
	public byte getType() {
		return TokenMap.Commands.HLO;
	}

	@Override
	public String toString() {
		return "You are " + power + "\nYour passcode is " + passcode;
	}

	@Override
	public void doNonQueryIO(TokenIO tokenIO, TokenGroup tokens) throws ParseException {
		power = (StandardToken) tokenIO.doToken(tokenIO.subGroup(tokens), power, TokenMap.powers);
		passcode = tokenIO.doInteger(tokenIO.subGroup(tokens), passcode);
		variantOptions = tokenIO.doTokenList(tokenIO.subGroup(tokens), variantOptions, TokenMap.groupAndOther(TokenMap.parameters));
		
	}

	/**
	 * Get the variant option for the parameter token specified.
	 * 
	 * @param search
	 *        A <code>StandardToken</code> of type PARAMETER which specifies
	 *        the type of option to look for.
	 * @return A <code>TokenGroup</code> which contains the variant option we
	 *         are looking for. Calling <code>next()</code> on this variant
	 *         will result in the integer option being returned.
	 */
	private TokenGroup getVariantOption(StandardToken search) {
		try {
			for (Token tok : variantOptions) {
				((TokenGroup) tok).reset();
				StandardToken firstTok = (StandardToken) ((TokenGroup) tok).next();
				if (firstTok.equals(search)) {
					return (TokenGroup) tok;
				}
			}
		}
		catch (ClassCastException e) {

		}
		return null;
	}

	/**
	 * Checks if the specified variant option is present in the list.
	 * 
	 * @param search
	 *        A <code>StandardToken</code> of type PARAMETER which specifies
	 *        the type of option to look for.
	 * @return true if the variant is present, false otherwise.
	 */
	private boolean isVariantPresent(StandardToken search) {
		return getVariantOption(search) != null;
	}

	/**
	 * Gets the integer associated with the specified variant option.
	 * 
	 * @param search
	 *        A <code>StandardToken</code> of type PARAMETER which specifies
	 *        the type of option to look for.
	 * @return An the integer value of this option, or -1 if one is not present.
	 * @throws ParseException
	 *         If the variant option was found but had no integer present.
	 */
	private int getVariantInteger(StandardToken search) throws ParseException {
		try {
			TokenGroup levelGroup = getVariantOption(search);
			return (Integer) levelGroup.next().getData();
		}
		catch (ClassCastException e) {
			return -1;
		}
		catch (NullPointerException e) {
			return -1;
		}
		catch (NoSuchElementException e) {
			throw new ParseException("There was no integer token for the specified parameter and one was expected");
			// TODO could have an option to simply ignore a time limit command
			// if there is no level specified.
		}

	}

	/**
	 * If true, then any legal syntax for an order will be accepted, even if it
	 * is illegal.
	 * 
	 * @return True if any order is accepted.
	 */
	public boolean isAnyOrdersAccepted() {
		return anyOrdersAccepted;
	}

	/**
	 * Gets the time limit in seconds that we have to submit orders on a build
	 * turn
	 * 
	 * @return The build turn time limit.
	 */
	public int getBuildTimeLimit() {
		return buildTimeLimit;
	}

	/**
	 * If true, the game will pause if one of the players disconnects.
	 * 
	 * @return True if deadlines stop on disconnect.
	 */
	public boolean isDeadlineDisconnect() {
		return deadlineDisconnect;
	}

	/**
	 * Gets the time limit in seconds that we have to submit orders on a
	 * movement turn
	 * 
	 * @return The movement turn time limit.
	 */
	public int getMovementTimeLimit() {
		return movementTimeLimit;
	}

	/**
	 * Gets the time limit in seconds that we have to submit orders on a retreat
	 * turn
	 * 
	 * @return The retreat turn time limit.
	 */
	public int getRetreatTimeLimit() {
		return retreatTimeLimit;
	}

	/**
	 * Gets the syntax level this game is operating at.
	 * 
	 * @return The syntax level for this game.
	 */
	public int getSyntaxLevel() {
		return syntaxLevel;
	}

}
